﻿using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Windows.Forms;
using Bluebird.Printer;
using Dfass.Retail.Configurations;
using Dfass.Retail.Enums;
using Dfass.Retail.Html;
using Dfass.Retail.JsonNet;
using Dfass.Retail.Printing;
using InTheHand.Net;
using InTheHand.Net.Bluetooth;
using InTheHand.Net.Sockets;
using Microsoft.Win32;
using Newtonsoft.Json;

namespace Dfass.Retail
{
    public partial class MainFrm : Form {
        private BluetoothListener _lsnr;
        private StreamWriter _connWtr;
        volatile bool _closing;
        private PrinterUtils _printerUtils;

        private string _fileFullname = null;

        private ManualResetEvent _manualEvent;

        public MainFrm()
        {
            InitializeComponent();
        }

        public void CrewTerminal_Load(object sender, EventArgs e) {
            SetRadioMode(RadioMode.Discoverable);
            StartBluetooth();
            AddMessage(MessageSource.Info,
                "Connect to another remote device running the app."
                + "  Each person can then enter text in the box at the bottom"
                + " and hit return to send it."
                + "  Of course the radio on the target device will have to be"
                + " in connectable and/or discoverable mode.");
        }

        #region Bluetooth start/connect/listen
        private void StartBluetooth()
        {
            try
            {
                new BluetoothClient();
            }
            catch (Exception ex)
            {
                var msg = "Bluetooth init failed: " + ex;
                MessageBox.Show(msg);
                throw new InvalidOperationException(msg, ex);
            }
            // TODO Check radio?
            //
            // Always run server?
            StartListener();
        }

        private void StartListener()
        {
            var lsnr = new BluetoothListener(Configs.OurServiceClassId);
            lsnr.ServiceName = Configs.OurServiceName;
            lsnr.Start();
            _lsnr = lsnr;
            ThreadPool.QueueUserWorkItem(ListenerAccept_Runner, lsnr);
        }

        void ListenerAccept_Runner(object state)
        {
            var lsnr = _lsnr;
            // We will accept only one incoming connection at a time. So just
            // accept the connection and loop until it closes.
            // To handle multiple connections we would need one threads for
            // each or async code.            
            while (true)
            {
                var conn = lsnr.AcceptBluetoothClient();
                var strm = conn.GetStream();
                SetConnection(strm, false, conn.RemoteEndPoint);
                ReadAllBytes(strm);
                ConnectionCleanup();
            }
        }
        #endregion

        #region Connection Set/Close
        private void SetConnection(Stream peerStream, bool outbound, BluetoothEndPoint remoteEndPoint)
        {
            if (_connWtr != null)
            {
                AddMessage(MessageSource.Error, "Already Connected!");
                return;
            }
            _closing = false;
            var connWtr = new StreamWriter(peerStream);
            connWtr.NewLine = "\r\n"; // Want CR+LF even on UNIX/Mac etc.
            _connWtr = connWtr;
//            ClearScreen();
            AddMessage(MessageSource.Info,
                (outbound ? "Connected to " : "Connection from ")
                // Can't guarantee that the Port is set, so just print the address.
                // For more info see the docs on BluetoothClient.RemoteEndPoint.
                + remoteEndPoint.Address);
        }

        private void ConnectionCleanup()
        {
            _closing = true;
            var wtr = _connWtr;
            //_connStrm = null;
            _connWtr = null;
            if (wtr != null)
            {
                try
                {
                    wtr.Close();
                }
                catch (Exception ex)
                {
                    Debug.WriteLine("ConnectionCleanup close ex: " + MakeExceptionMessage(ex));
                }
            }
        }

        void BluetoothDisconnect()
        {
            AddMessage(MessageSource.Info, "Disconnecting");
            ConnectionCleanup();
        }
        #endregion

        #region Connection I/O
        private bool Send(string message)
        {
            if (_connWtr == null)
            {
                MessageBox.Show("No connection.");
                return false;
            }
            try
            {
                _connWtr.WriteLine(message);
                _connWtr.Flush();
                return true;
            }
            catch (Exception ex)
            {
                MessageBox.Show("Connection lost! (" + MakeExceptionMessage(ex) + ")");
                ConnectionCleanup();
                return false;
            }
        }

        public void ReadAllBytes(Stream strm)
        {
            var reader = new BinaryReader(strm);
            const int fixedBufferSize = 1024;//4096;
            const int headerSize = 12;// 2 + 10
            const int headerTypeSize = 2;
            const int headerLengthSize = 10;
            var headerBuf = new byte[headerSize];            
            int headercount;
            try {
                _printerUtils = new PrinterUtils();
                while (true) {
                    int HeaderReadPos = 0;
                    var headerRemaining = headerSize;
                    while (headerRemaining > 0) {
                        headercount = reader.Read(headerBuf, HeaderReadPos, headerRemaining);

                        headerRemaining -= headercount;
                        HeaderReadPos += headercount;
                    }
                    var delimiterType =
                        Convert.ToInt16(System.Text.Encoding.Default.GetString(headerBuf, 0, headerTypeSize));
                    var delimiterLength =
                        Convert.ToInt32(System.Text.Encoding.Default.GetString(headerBuf, 0 + headerTypeSize,
                            headerLengthSize));
                    var remaining = delimiterLength;
                    if (delimiterType == (int) StreamDataType.Binary || delimiterType == (int) StreamDataType.String) {

                        // binary type                        
                        int bufferSize;
                        using (var ms = new MemoryStream()) {
                            while (remaining > 0) {
                                if (remaining - fixedBufferSize < 0) {
                                    bufferSize = remaining;
                                }
                                else {
                                    bufferSize = fixedBufferSize;
                                }
                                var buffer = new byte[bufferSize];
                                int count = reader.Read(buffer, 0, bufferSize);
                                ms.Write(buffer, 0, count);
                                if (count <= 0) {
                                    throw new EndOfStreamException
                                        (String.Format("100, End of stream reached with {0} bytes left to read",
                                            remaining));
                                }
                                remaining -= count;
                            }

                            if (delimiterType == (int) StreamDataType.Binary) {
                                PrintBmp(ms.ToArray());
                            }
                            else if (delimiterType == (int) StreamDataType.String) {
                                PrintHtml(ms);
                            }
                        }
                    }
                    else { // 99: end of the stream                        
                        // send ack back to indicate printing was successful
                        var jMsg = new JMessage();
                        jMsg.Ttl = "Printing";
                        jMsg.Msg = "Printing is successful";
                        var jStr = JsonConvert.SerializeObject(jMsg);
                        Send(jStr);

                        _printerUtils.PrinterClose();
                        return;                    
                    }
                }
            }
            catch (IOException ioex) {
                MessageBox.Show("101, Connection is stopped before anything is read.");
            } catch (Exception ex) {
                MessageBox.Show("101, Connection is stopped from tablet side.");
            }
            _printerUtils.PrinterClose();
        }

        private void PrintBmp(byte[] content)
        {
            _manualEvent = new ManualResetEvent(false);
            var WriteBmpThread = new Thread(() => {
                // EN : Verify path of program files folder ( because it is different by each language differences)
                RegistryKey reg = Registry.LocalMachine.OpenSubKey(@"\System\Explorer\Shell Folders", false);

                if (reg != null)
                {
                    // EN : Check modul type
                    _fileFullname = reg.GetValue("Program Files") as string;

                    reg.Close();
                }
                else
                {
                    _fileFullname = @"\Program Files";
                }
                _fileFullname += @"\crewterminal\AC.bmp";
                var fileStrm = new FileStream(_fileFullname, FileMode.Create);
                fileStrm.Write(content, 0, content.Count());

                fileStrm.Close();                
                //            AddMessage(MessageSource.Remote, content);

                _manualEvent.Set();
            });
            WriteBmpThread.Start();

            _manualEvent.WaitOne();

            // print            
            _printerUtils.PrintBitmap(_fileFullname);
            _manualEvent.Close();
        }

        
        private void PrintHtml(Stream strm) {
            var rdr = new StreamReader(strm);
            strm.Position = 0;
            uint alignment;
            while (true) {
                string line;
                try {
                    line = rdr.ReadLine();
                    string newLine;
                    alignment = GetAlignment(line, out newLine);
                    line = newLine;
                } catch (IOException ioex) {
                    if (_closing) {
                        // Ignore the error that occurs when we're in a Read
                        // and _we_ close the connection.
                    } else {
                        AddMessage(MessageSource.Error, "Read line failed.  "
                            + MakeExceptionMessage(ioex));
                    }
                    break;
                }
                if (line == null) {
                    AddMessage(MessageSource.Info, "Reached end of the current html");
                    break;
                }
                _printerUtils.PrintText(line + "\r\n", alignment);
            }//while  
        }

        private uint GetAlignment(string line, out string newLine) {
            newLine = line;
            if (line != null) {
                var openIndex = newLine.IndexOf(SimpleHtmlTags.UnderlineOpenTag);
                var closeIndex = newLine.IndexOf(SimpleHtmlTags.UnderlineCloseTag);

                if (openIndex != -1 && closeIndex != -1) {
                    newLine = newLine.Remove(closeIndex, SimpleHtmlTags.UnderLineCloseTagLength);
                    newLine = newLine.Remove(openIndex, SimpleHtmlTags.UnderLineOpenTagLength);

                    return Printer.OPTION_UNDERLINE;
                }
            }
            return Printer.OPTION_DEFAULT;
            
        }

        private static string MakeExceptionMessage(Exception ex)
        {
#if !NETCF
            return ex.Message;
#else
            // Probably no messages in NETCF.
            return ex.GetType().Name;
#endif
        }
        #endregion        

        #region Radio
        private void SetRadioMode(RadioMode mode)
        {
            try
            {
                BluetoothRadio.PrimaryRadio.Mode = mode;
            }
            catch (NotSupportedException)
            {
                MessageBox.Show("Setting Radio.Mode not supported on this Bluetooth stack.");
            }
        }
        #endregion

        void AddMessage(MessageSource source, string message)
        {
            EventHandler action = delegate
            {
                string prefix;
                switch (source)
                {
                    case MessageSource.Local:
                        prefix = "Me: ";
                        break;
                    case MessageSource.Remote:
                        prefix = "You: ";
                        break;
                    case MessageSource.Info:
                        prefix = "Info: ";
                        break;
                    case MessageSource.Error:
                        prefix = "Error: ";
                        break;
                    default:
                        prefix = "???:";
                        break;
                }
                AssertOnUiThread();
                msgTb.Text =
                    prefix + message + "\r\n"
                    + msgTb.Text;
            };
            ThreadSafeRun(action);
        }

        private void ThreadSafeRun(EventHandler action)
        {
            Control c = msgTb;
            if (c.InvokeRequired)
            {
                c.BeginInvoke(action);
            }
            else
            {
                action(null, null);
            }
        }

        private void AssertOnUiThread()
        {
            Debug.Assert(!this.msgTb.InvokeRequired, "UI access from non UI thread!");
        }

        public void CrewTerminal_Closing(object sender, CancelEventArgs e) {
            var result = MessageBox.Show("Quit?", "Quit?",
                MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2);
            if (result != DialogResult.Yes) {
                e.Cancel = true;
            }
        }

        private void startListeningMi_Click(object sender, EventArgs e) {
            CrewTerminal_Load(sender, e);
        }

        private void setRadioModeMi_Click(object sender, EventArgs e)
        {

        }

        private void DiscoverableMi_Click(object sender, EventArgs e)
        {
            SetRadioMode(RadioMode.Discoverable);
        }

        private void stopListeningMi_Click(object sender, EventArgs e)
        {
            BluetoothDisconnect();
        }

        private void connectableMi_Click(object sender, EventArgs e)
        {
            SetRadioMode(RadioMode.Connectable);
        }

        private void neitherMi_Click(object sender, EventArgs e)
        {
            SetRadioMode(RadioMode.PowerOff);
        }
    }
}